/* 硬件根类 */
// rxjs参考 https://cn.rx.js.org/manual/overview.html#h213
var child_process
try {
    child_process = require("child_process");

} catch (error) {
    console.log('系统不支持require操作，请忽略');
}

var first = require('rxjs/operators/first').first;
var filter = require('rxjs/operators/filter').filter;
var bufferTime = require('rxjs/operators/bufferTime').bufferTime;

let Dimension = require('./lib/Dimension').Dimension
const ws = require('nodejs-websocket');
const querystring = require('querystring');
import {
    ObjectDimension
} from "./lib/Dimension"
let url = require('url');
class Hardware {
    /* 生成说明文档 */
    generateDocument() {
        return `
        一般硬件具备的功能：
        0 _prepareSubject()函数会解释实例的data数据，并生成消息主题，以供监听
        1 _bindListener()函数会绑定 onSocketText 的事件，并调用onText()函数；会监听childConnections的状态变化 ，并向上层上报数据
        1 _startSocketServer() 开启socket服务器 
        2 _connectToServer() 连接到上级的socket服务器，并具备自动监听断连功能，自动重连
        3 _startHttpServer() 开启http服务器，并可根据url开发响应事件
        4 mounted() 函数 可在扩展类里重新定义，在原有功能的基础上，开发更丰富的功能 
        `
    }
    constructor(uid, config) {

        this.config = config

        this._setParentServerIp() //设置上级url

        this.hardwareList = {}
        this.uid = uid

        this.server

        this.events = {
            "open": this.open,
            "hibernate": this.hibernate,
            "wakeup": this.wakeup,
            "shutdown": this.shutdown,
            "status": this.status,
        }

        this.connToServer = null
        this.data = {
            thisHttpServerStatus: false, //本机的http服务器
            onHttpRequest: false, //收到的http请求
            onSocketText: false, //收到的socket请求
            thisSocketServerStatus: false, //本机的socker服务器
            parentSocketServerConnStatus: false, //父级socket服务器状态
            parentSocketServerPong: false, //父级socket服务器心跳状态
            monitor: false, //父级socket服务器监视功能
            net: {
                ip: '192.168.10.1',
                eth: 'eth0',
                gw: '192.168.1.1',
            },
            childConnections: {}, //用于保存下级节点的数据，包含有完整的树状结构，用于ui或者数据显示
            info: '', // 通知的主题
            security: {
                client: { //作为客户端的安全相关信息

                },
                server: { //作为服务器的安全相关信息
                    privateKey: '', //私钥
                    publicKey: '', //公钥 
                    md5Key: '', //用于解密的密码 
                },

            }
        }
        this.controls = {
            shutdown: 'shutdown',
            wakeup: 'wakeup',
            hibernate: 'hibernate',
            open: 'open',
            initComputer: 'initComputer',
            shutdownComputer: 'shutdownComputer',
        }
        this.childConnections = {}
        this.mounted()
    }


    /* 本函数是放开来让下级的节点上报数据使用的，调用此方法，会触发下面几个步骤：
    1 this.dataRoot.$(this.dataRoot, 'childConnections').setValue
    2 由于在_bindListener()里绑定了监听，childConnections数据变化 ，会触发 upLoadStatus 上报数据
    3 父级数据也会一层一层向上报，直到最顶层
    */
    _updateConnectionData(msg) {
        let key = msg.query.key
        let data = msg.body
        this.dataRoot.$(this.dataRoot, 'childConnections').updateOrAddProperty(key, data)
        console.log(this.uid + ' 已更新数据:', this.dataRoot.$(this.dataRoot, 'childConnections').toJson());
    }

    _prepareSubject() {
        this.dataRoot = new ObjectDimension('root', this.data)
    }

    _bindListener() {
        let that = this

        //如果下级的数据变化了，触发上报上级的算法（函数）
        this.dataRoot.$(this.dataRoot, 'childConnections').infoSubject.subscribe(function (param) {
            let data = that.dataRoot.$(that.dataRoot, 'childConnections').toJson()
            that.upLoadStatus(data)
        })

        this.dataRoot.$(this.dataRoot, 'childConnections').propertyAddSubject.subscribe(function (param) {
            let key = param.key

        })
        this.dataRoot.$(this.dataRoot, 'childConnections').propertyUpdateSubject.subscribe(function (param) {
            let key = param.key
        })

        this.dataRoot.$(this.dataRoot, 'onSocketText').valueChangeSubject.subscribe(function (param) {
            that._onSocketText(param.newValue)
        })

        that.dataRoot.$(that.dataRoot, 'onHttpRequest').valueChangeSubject.subscribe(function (param) {
            that._onHttpRequest(param.newValue)
        })

        this.dataRoot.$(this.dataRoot, 'onSocketText').valueChangeSubject
            .pipe(filter(param => param.newValue.path == "/getStatus")) // 请求来到此处
            .subscribe(function (param) {
                let msg = param.newValue
                let conn = msg.headers.conn
                let connData = that.dataRoot.$(that.dataRoot, 'childConnections').toJson()
                let msgToSend = {
                    id: '',
                    path: 'feedbackStatus',
                    query: {
                        id: 1
                    },
                    headers: {},
                    body: connData
                }
                conn.sendText(JSON.stringify(msgToSend))
            })
        this.dataRoot.$(this.dataRoot, 'onSocketText').valueChangeSubject
            .pipe(filter(param => param.newValue.path == "/childConnections")) // 请求来到此处
            .subscribe(function (param) {
                let childConnections = that.dataRoot.$(that.dataRoot, 'childConnections').toJson()
                let headers = param.newValue.headers
                let conn = headers.conn
                let msgToSend = {
                    id: '',
                    path: '/status',
                    query: {},
                    headers: {},
                    body: childConnections
                }
                conn.sendText(JSON.stringify(msgToSend))
            })

        this.dataRoot.$(this.dataRoot, 'thisSocketServerStatus').valueChangeSubject
            .pipe(filter(param => param.newValue == true)) //连接状态变成断开
            .subscribe(function (param) {
                console.log(that.uid + " 本机socket服务器：启动成功");
            })
        this.dataRoot.$(this.dataRoot, 'thisSocketServerStatus').valueChangeSubject
            .pipe(filter(param => param.newValue == false)) //连接状态变成断开
            .subscribe(function (param) {
                console.log(that.uid + " 本机socket服务器：已断开");
                that._startSocketServer()
            })



        this.dataRoot.$(this.dataRoot, 'parentSocketServerConnStatus').valueChangeSubject
            .pipe(filter(param => param.newValue == false)) //连接状态变成断开
            .subscribe(function (param) {
                console.log(that.uid + " 断开!!!", param.newValue);
                that._connectToServer()
            })

        this.dataRoot.$(this.dataRoot, 'parentSocketServerConnStatus').valueChangeSubject
            .pipe(filter(param => param.newValue == true)) //连接状态变成已连接
            .subscribe(function (param) {
                console.log(that.uid + "已连接至服务器的socket：" + that.parentServerUrl);
                //向服务器发送注册通知
                let conn = that.connToServer
                let msg = {
                    id: '',
                    path: 'registry',
                    query: {
                        name: that.uid
                    },
                    headers: {},
                    body: '',
                }
                conn.sendText(JSON.stringify(msg)) //发送注册通知
                conn.sendPing('hello') //发送心跳包
                that.upLoadStatus() //向上级上报数据
            })

        let monitorInterval
        this.dataRoot.$(this.dataRoot, 'monitor').valueChangeSubject
            .pipe(filter(param => param.newValue == true)) //开启监视器
            .subscribe(function (param) {
                monitorInterval = setInterval(() => { //每5秒向服务器ping数据
                    that.dataRoot.$(that.dataRoot, 'parentSocketServerPong').setValue(false)
                    if (that.config.parentIp) { //如果有配置上级服务器
                        try {
                            that.connToServer.sendPing('') //发送心跳ping
                        } catch (err) {

                        }
                        setTimeout(() => {
                            let value = that.dataRoot.$(that.dataRoot, 'parentSocketServerPong').getValue()
                            console.log(that.uid + ' pong健康状态：', value);
                            if (!value) { //如果健康状态不佳
                                console.log(that.uid, ' 通过写false数据触发重连服务器...');
                                that.dataRoot.$(that.dataRoot, 'parentSocketServerConnStatus').setValue(false)
                            }
                        }, 2000);
                    }
                }, 5000);
            })

        this.dataRoot.$(this.dataRoot, 'monitor').valueChangeSubject
            .pipe(filter(param => param.newValue == false)) //关闭监视器
            .subscribe(function (param) {
                clearInterval(monitorInterval)
            })

    }

    _setParentServerIp() {
        let config = this.config
        this.parentIp = config.parentIp
        this.parentServerUrl = config.parentIp + ':' + config.parentSocketPort //上级硬件的socket地址
    }

    /* 开启http服务器 */
    _startHttpServer() {
        let that = this
        let config = this.config
        var http = require('http');
        let httpPort = config.httpPort

        var server = http.createServer(function (request, response) {
            // 示例
            // response.writeHead(200, { 'Content-Type': 'text/plain' }); //写返回头
            // response.end('Hello World\n'); //写返回内容

            console.log('接收到http请求：', request.url);

            let msg = { //构造请求信息 
                path: request.url.split('?')[0],
                headers: {
                    request: request,
                    response: response,
                },
                query: {},
                body: '',
            }

            try { //解释query
                let query = request.url.split('?')[1]
                msg.query = querystring.parse(query)
            } catch (err) {

            }

            var body = "";
            request.addListener("data", function (chunk) {
                body += chunk;
            });

            request.addListener("end", function () {
                if (body) { //解释请求体 body
                    // var obj = querystring.parse(body);
                    // headers['body'] = obj
                    msg.body = body
                }
                // 发送请求信号
                console.log(msg);
                that.dataRoot.$(that.dataRoot, 'onHttpRequest').setValue(msg)
            });
        })
        server.listen(httpPort);
        if (server) {
            console.log(that.uid + ' http服务器启动成功：' + httpPort);
        }

    }

    /* 开启socket服务 */
    _startSocketServer() {
        let that = this
        let config = this.config
        let socketPort = config.socketPort
        let server = ws.createServer(conn => {

            // let interval = setInterval(() => { //每一分钟检查连接的健康状态
            //     let name = conn['name']
            //     if (name) {
            //         conn['pongStatus'] = false
            //         that.dataRoot.$(that.dataRoot, 'childConnections.' + name).updateOrAddProperty('pongStatus', false)
            //         try {
            //             conn.sendPing('') //发送心跳ping
            //         } catch (err) {

            //         }
            //         setTimeout(() => {
            //             if (!conn['pongStatus']) {
            //                 that.dataRoot.$(that.dataRoot, 'childConnections').deleteProperty(name)
            //                 delete that.childConnections[name]
            //                 clearInterval(interval)
            //             }
            //         }, 2000);
            //     }
            // }, 10000);

            conn.on('text', msg => {
                let rec = JSON.parse(msg)
                console.log(rec);
                try {
                    rec.headers['conn'] = conn
                } catch (err) {
                    
                }
                that.dataRoot.$(that.dataRoot, 'onSocketText').setValue(rec) //通知事件
            });
            conn.on('pong', data => {
                let name = conn['name']
                console.log(that.uid + '的子连接 ' + name + " 收到心跳包");
                conn['pongStatus'] = true
                that.dataRoot.$(that.dataRoot, 'childConnections.' + name).updateOrAddProperty('pongStatus', true)
                that.dataRoot.$(that.dataRoot, 'childConnections.' + name).updateOrAddProperty('lastPing', new Date().getTime())
            });
            conn.on('error', data => {
                console.log(that.uid + " 出现错误");
            });
            conn.on('close', data => {
                console.log(that.uid + " 退出一个客户端");
                this.dataRoot.$(this.dataRoot, 'childConnections').deleteProperty(conn['name'])
                clearInterval(interval)
            });
        });
        this.server = server /

            server.listen(socketPort, () => {
                console.log(that.uid + " Socket Running on :" + socketPort);
                that.dataRoot.$(that.dataRoot, 'info').valueChangeSubject.next({
                    path: '/method/_startSocketServer',
                    code: 200,
                    body: 'ok',
                })
            });

        server.on('listening', msg => {
            that.dataRoot.$(that.dataRoot, 'thisSocketServerStatus').setValue(true) //通知启动成功
        });
        server.on('close', msg => {
            that.dataRoot.$(that.dataRoot, 'thisSocketServerStatus').setValue(false) //通知连接断开
        });
        server.on('error', msg => {
            // that.dataRoot.$(that.dataRoot, '').setValue(false) //通知连接成功
        });
    }

    /* 作为客户端连接到其它服务器 */
    _connectToServer() {
        let that = this
        if (this.parentIp) {
            let conn = ws.connect('ws://' + this.parentServerUrl, function (conn) {
                // console.log(conn);
            })
            conn.on('connect', msg => {
                that.connToServer = conn
                that.dataRoot.$(that.dataRoot, 'parentSocketServerConnStatus').setValue(true) //通知连接成功
            })
            // 作为客户端收到信息
            conn.on('text', msg => {
                let rec = JSON.parse(msg)
                that._onSocketText(rec, conn)
                that.dataRoot.$(that.dataRoot, 'onSocketText').setValue(rec) //通知事件
            });
            conn.on('error', data => {
                console.log("与服务器的主动连接出现错误");
                that.dataRoot.$(that.dataRoot, 'parentSocketServerConnStatus').setValue('ok') //通知连接成功
            });
            conn.on('pong', data => {
                console.log("收到心跳包");
                that.dataRoot.$(that.dataRoot, 'parentSocketServerPong').setValue(true)
            });
            conn.on('close', data => {
                console.log("已退出与服务器的主动连接");
                that.dataRoot.$(that.dataRoot, 'parentSocketServerConnStatus').setValue('error')
            });
        }

    }

    /* 
    msg.path      控制类型： registry 注册 ；其它 控制
    msg.query.id        硬件的唯一id标识
    msg.query.name 调用的事件名 ， 可以用 . 来表示下一级（例如pi.shutdown,表示调用本硬件下的pi节点的shutdown）。 
                        如无，则表示调用本硬件的函数
    msg.body    调用的数据
    */
    _onSocketText(msg) {
        let that = this
        try {
            console.log(msg.path);
            let path = msg.path
            if (path == 'registry') { // 下级节点要求注册到父节点（本节点）的信息
                that._registry(msg)
            } else if (path == 'control') { //控制信息发送
                that._control(msg)
            } else if (path == 'data') { // 完善下级信息
                that._updateConnectionData(msg)
            } else { //更多功能请在 mounted() 中开发
                console.log("等待其它控制器处理...");
            }
        } catch (err) {
            console.log(err);
            console.log('not a json msg')
        }
    }

    /* 
    msg.path      请求路径
    msg.query     请求参数
    msg.headers   请求头
    msg.body      请求数据
    */
    _onHttpRequest(msg) {
        let that = this
        try {
            let path = msg.path
            console.log('接收到http请求：', path);
        } catch (err) {
            console.log(err);
            console.log('not a json msg')
        }
    }

    /* 
    msg..id     硬件的唯一id标识
    */
    _registry(msg) {
        let that = this
        console.log('下级节点要求注册到父节点（本节点）的信息');
        this.childConnections[msg.query.name] = msg.headers.conn
        msg.headers.conn['name'] = msg.query.name
        for (const key in this.childConnections) {
            console.log("名下的节点：");
            console.log(key);
        }
    }

    /* 生成公私钥
    1 服务器生成公私钥，保存到 data的 security.server.privateKey 和 security.server.publicKey
    2 当有下级连接成功后，发放公钥到下级硬件
    3 下级硬件使用公钥加密 md5 的编码密码,加密后回传到服务器
    4 服务器用私钥解密后，将密码放到 security.server.md5Key 里
    5 与下级服务器的通信，使用 security.server.md5Key 来加密、解密
    */
    _genRsaKeyPair() {

    }

    /* 
    msg.id        硬件的唯一id标识
    msg.query.name 调用的事件名 ， 可以用 . 来表示下一级（例如pi.shutdown,表示调用本硬件下的pi节点的shutdown）。 
                        如无，则表示调用本硬件的函数
    msg.body    调用的一些具体参数
    */
    _control(msg) {
        let that = this
        let body = msg.body
        let key = msg.query.name
        let deepth = key.indexOf('.')
        if (deepth == -1) {
            console.log('调用本硬件事件')
            that[key](msg.body) //调用可响应的事件
        } else {
            console.log('调用下一层级硬件事件')
            let thisNodeKey = this._getThisNodeKey(key)
            console.log('thisNodeKey');
            console.log(thisNodeKey);
            let subNodeKey = this._getSubNodeKey(key)
            console.log('subNodeKey');
            console.log(subNodeKey);
            // let msgToSend = {
            //     type: 'method',
            //     eventName: subNodeKey,
            //     params: params
            // }
            let msgToSend = {
                id: '',
                path: 'control',
                query: {
                    name: subNodeKey
                },
                headers: {},
                params: body
            }
            console.log('msgToSend');
            console.log(msgToSend);
            this.childConnections[thisNodeKey].sendText(JSON.stringify(msgToSend))
        }
    }

    _sendText() {

    }

    _genRandom() {
        return parseInt(Math.random() * 1000000).toString()
    }


    _getThisNodeKey(key) {
        let list = key.split('.')
        return list[0]
    }

    _getSubNodeKey(key) {
        let list = key.split('.')
        let str = ''
        for (let i = 1; i < list.length; i++) {
            const l = list[i];
            if (i < list.length - 1) {
                str += l + '.'
            } else(
                str += l
            )
        }
        return str
    }

    /* 
    找到下级节点 ， 新建一个事件id , 创建一个监听主题（设置超时），收集到所有子id时结束监听，并写入数据
    如果是中间层节点，会在等待时间结束后，将数据设置到 childConnections 里，并触发 upLoadStatus 
    如果是最低层节点，则直接会收集硬件数据，并直接执行 upLoadStatus
    */
    traverse(param) {
        let that = this
        let eventId = null
        try {
            eventId = param.eventId
        } catch (err) {

        }
        let childConnections = this.childConnections
        let length = 0
        for (const key in childConnections) {
            if (Object.hasOwnProperty.call(childConnections, key)) {
                const conn = childConnections[key];
                let msgToSend = {
                    id: eventId,
                    path: 'control',
                    query: {
                        name: 'traverse'
                    },
                    headers: {},
                    body: {},
                }
                conn.sendText(JSON.stringify(msgToSend))
                length++
            }
        }
        console.log('connected length:', length);
        if (length > 0) { // 中间层节点

        } else { // 最底层节点
            // 更新数据
            that.upLoadStatus()
        }

        that.dataRoot.$(that.dataRoot, 'info').valueChangeSubject.next({
            path: '/method/traverse',
            code: 200,
            body: 'ok',
        })

    }

    /* 向上级服务器上报数据
    0 在本硬件连接上级服务器成功后，除了会向上级注册，也同时会调用此事件来上报各种数据
    1 一般在 this.data.childConnections 数据发生变化的情况下上报一次
    2 如果上级节点发信号调用 traverse()函数，也会触发本事件
    */
    upLoadStatus(childrenDataStr, eventId = null) {
        let that = this
        let connToServer = this.connToServer
        if (connToServer) { //如果有上级，上报当前节点数据
            let childConnections = this.dataRoot.$(this.dataRoot, 'childConnections').toJson()
            let dataToSend = {
                lastUpdate: new Date(),
                controls: that.controls,
                children: childConnections,
            }
            let msgToSend = {
                eventId: eventId,
                path: 'data',
                query: {
                    name: 'childConnections',
                    key: that.uid,
                },
                headers: {},
                body: dataToSend,
            }
            console.log(this.uid + ' 上报当前节点数据：', JSON.stringify(msgToSend));
            connToServer.sendText(JSON.stringify(msgToSend))
        } else { //无上级，则当前节点收到所有数据
            that.dataRoot.$(that.dataRoot, 'info').valueChangeSubject.next({
                path: '/method/upLoadStatus',
                code: 200,
                body: 'ok',
            })
        }
    }


    open() {
        console.log(this.uid + ' open()');
    }
    hibernate() {
        console.log(this.uid + ' hibernate()');
    }
    wakeup() {
        console.log(this.uid + ' wakeup()');
    }
    shutdown() {
        child_process.exec("shutdown now", function (err, stdout, stderr) {
            if (err) {
                console.error(err);
            }
            console.log("stdout:", stdout)
            console.log("stderr:", stderr);
        });
    }


    sentMsgToServer(msg) {
        let that = this
        let conn = this.connToServer
        conn.sendText(JSON.stringify(msg))
    }

    setIp(msg) {
        const net = this.net
        child_process.exec('ifconfig eth0 ' + net.ip + ' broadcast 192.168.1.255 netmask 255.255.255.0', function (err) {})
        child_process.exec('route add default gw ' + net.gw + ' dev ' + net.eth, function (err) {})
    }

    addHardware(name, path) {
        this.hardwareList[name] = path
    }

    /* 系统开启成功后，开启led灯 */
    openLed() {

    }

    mounted() {
        let that = this
        this._prepareSubject() //准备好主题
        /* 可以根据本实例的 _bindListener 函数，在继承类的 mounted() 里开发自己的处理逻辑 */
        this._bindListener()
        this._connectToServer() //连接上级服务器
        this._startSocketServer() //启动服务器
        this._startHttpServer() //启动http服务器

        that.dataRoot.$(that.dataRoot, 'monitor').setValue(true) //启动连接监视器

    }

}

export {Hardware}